package Application;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public abstract class WorkUnit<T, U> implements Serializable, Runnable {
	private static final long serialVersionUID = 1L;
	private int id;
	private int clientId;
	private List<T> inputs;
	private List<U> outputs;

	public WorkUnit(int id, int clientId) {
		this.id = id;
		inputs = new LinkedList<T>();
		outputs = new LinkedList<U>();
	}

	/**
	 * Overwrites and sets the list of inputs to the list passed in.
	 * 
	 * @param inputList
	 */
	public void setInputs(List<T> inputList) {
		inputs = new LinkedList<T>();
		addInputs(inputList);
	}

	/**
	 * Overwrites and sets the list of inputs to the array passed in.
	 * 
	 * @param inputArray
	 */
	public void setInputs(T[] inputArray) {
		inputs = new LinkedList<T>();
		addInputs(inputArray);
	}

	/**
	 * Adds the list of inputs passed in to the internal one.
	 * 
	 * @param inputList
	 */
	public void addInputs(List<T> inputList) {
		inputs.addAll(inputList);
	}

	/**
	 * Adds the array of inputs passed in to the internal list.
	 * 
	 * @param inputArray
	 */
	public void addInputs(T[] inputArray) {
		for (T input : inputArray) {
			inputs.add(input);
		}
	}

	/**
	 * Overwrites and sets the list of inputs to the values in the file passed
	 * in. Values must be of type T and separated by commas. Any white-space
	 * will be trimmed.
	 * 
	 * @param fileToRead
	 */
	public void setInputs(File fileToRead, Object typeToCast) {
		inputs = new LinkedList<T>();
		addInputs(fileToRead, typeToCast);
	}

	/**
	 * Adds the values of the file to the internal list of inputs. Values must
	 * be of type T and separated by commas and any white-space will be trimmed.
	 * The "typeOfCast" parameter is only used to indicate what type to parse
	 * the input from the file.
	 * 
	 * @param fileToRead
	 * @param typeOfParse
	 *            - Parameter only used to tell what type to cast input to.
	 */
	public void addInputs(File fileToRead, Object typeOfParse) {
		Scanner scanner = null;
		try {
			scanner = new Scanner(fileToRead);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		while (scanner.hasNext()) {
			String line = scanner.nextLine();
			String[] lineElements = line.split(",");
			for (String element : lineElements) {
				element = element.trim();
				if (typeOfParse instanceof Integer)
					inputs.add((T) new Integer(Integer.parseInt(element)));
				else if (typeOfParse instanceof Float)
					inputs.add((T) new Float(Float.parseFloat(element)));
				else if (typeOfParse instanceof Double)
					inputs.add((T) new Double(Double.parseDouble(element)));
				// String
				else
					inputs.add((T) element);
			}
		}
	}

	public int getId() {
		return id;
	}

	public int getClientId() {
		return clientId;
	}

	public List<T> getInputs() {
		return inputs;
	}

	public List<U> getOutputs() {
		return outputs;
	}

	@Override
	public final void run() {
		for (T input : inputs) {
			U output = doWork(input);
			if (output != null)
				outputs.add(output);
		}
	}

	/**
	 * Method to be overridden. This is where the meat of the work will be done.
	 * This method will be called once for every input, and the result of the
	 * processing should be returned. Return null to indicate that nothing is to
	 * be added to the list of outputs.
	 * 
	 * @param input
	 * @return
	 */
	public abstract U doWork(T input);

	/**
	 * Called when combining the results from various Work Units. Default
	 * behavior is to simply append the two lists of results together.
	 * 
	 * @param left
	 * @param right
	 * @return
	 */
	public List<U> onRecombine(List<U> other) {
		outputs.addAll(other);
		return outputs;
	}

	/**
	 * Preferred method of defining input. User should override this method to
	 * return a list of the inputs they want to WorkUnit to process.
	 * 
	 * @return
	 */
	public List<T> inputFactory() {
		return null;
	}
}